package top.jpower.jpower.config.filter;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import com.alibaba.fastjson2.JSON;
import top.jpower.jpower.config.MutableHttpServletRequest;
import top.jpower.jpower.dbs.entity.core.function.TbCoreDataScope;
import top.jpower.jpower.module.base.vo.ResponseData;
import top.jpower.jpower.module.common.auth.RoleConstant;
import top.jpower.jpower.module.common.auth.UserInfo;
import top.jpower.jpower.module.common.cache.CacheNames;
import top.jpower.jpower.module.common.deploy.props.JpowerProperties;
import top.jpower.jpower.module.common.redis.RedisUtil;
import top.jpower.jpower.module.common.support.ChainMap;
import top.jpower.jpower.module.common.utils.*;
import top.jpower.jpower.module.common.utils.constants.AppConstant;
import top.jpower.jpower.module.common.utils.constants.StringPool;
import top.jpower.jpower.module.common.utils.constants.TokenConstant;
import top.jpower.jpower.module.datascope.DataScope;
import top.jpower.jpower.module.properties.AuthDefExculdesUrl;
import top.jpower.jpower.module.properties.AuthProperties;
import top.jpower.jpower.service.core.role.CoreDataScopeService;
import top.jpower.jpower.service.core.role.CoreFunctionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static top.jpower.jpower.module.common.auth.RoleConstant.ROOT_ID;
import static top.jpower.jpower.module.common.utils.constants.TokenConstant.HEADER_MENU;

/**
 * @ClassName AuthFilter
 * @Description TODO BOOT项目鉴权登录
 * @Author 郭丁志
 * @Date 2020-08-31 16:13
 * @Version 1.0
 */
@Component
@Order(999)
@Slf4j
public class AuthFilter implements Filter {

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private AuthProperties authProperties;
    @Autowired
    private JpowerProperties jpowerProperties;
    @Autowired
    private CoreFunctionService coreFunctionService;
    @Autowired
    private CoreDataScopeService dataScopeService;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    /** 测试环境是否需要进行权限验证 **/
    @Value("${jpower.test.is-login:false}")
    private boolean isLogin;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest)request;
//        开发环境不走鉴权，测试环境判断是否开启了鉴权
        if (Fc.equals(jpowerProperties.getEnv(), AppConstant.DEV_CODE) || (Fc.equals(jpowerProperties.getEnv(), AppConstant.TEST_CODE) && Fc.equals(isLogin,false))){
            chain.doFilter(request, response);
            return;
        }

        String currentPath = httpRequest.getServletPath();

        //不鉴权得URL
        if (isSkip(currentPath)){
            chain.doFilter(request, response);
            return;
        }

        String token = JwtUtil.getToken(httpRequest);
        if (Fc.isNotBlank(token)){

            if (!redisUtil.exists(CacheNames.TOKEN_URL_KEY + token)){
                ResponseData<String> responseData = ReturnJsonUtil.print(HttpStatus.PROXY_AUTHENTICATION_REQUIRED.value(),"令牌已过期，请重新登录",false);

                WebUtil.renderJson((HttpServletResponse) response,responseData);
                return;
            }

            UserInfo user = ShieldUtil.getUser(httpRequest);
            if (Fc.isNull(user) || !isAuthByToken(token, currentPath)) {
                ResponseData<String> responseData = ReturnJsonUtil.print(HttpStatus.UNAUTHORIZED.value(),"请求未授权",false);
                WebUtil.renderJson((HttpServletResponse) response,responseData);
                return;
            }

            Object dataAuth = redisUtil.get(CacheNames.TOKEN_DATA_SCOPE_KEY + token);
            Map<String,List> map = Fc.isNull(dataAuth) ? ChainMap.<String,List>create().build() : (Map<String, List>) dataAuth;
            chain.doFilter(addHeader(httpRequest, StringPool.EMPTY, JSON.toJSONString(map.getOrDefault(httpRequest.getHeader(HEADER_MENU), ListUtil.empty()))), response);
            return;
        }else {
            //白名单
            String ip = WebUtil.getIp(httpRequest);
            if (Fc.contains(authProperties.getWhileIp(),ip)){
                chain.doFilter(addHeader(httpRequest,ip, StringPool.EMPTY), response);
                return;
            }

            //匿名用户
            List<String> listUrl = coreFunctionService.queryUrlAnonymousRole();
            if(isAuth(listUrl, currentPath)){

                String menuCode = httpRequest.getHeader(HEADER_MENU);
                List<DataScope> dataScopes = null;
                if (Fc.isNotBlank(menuCode)){
                    List<TbCoreDataScope> list = dataScopeService.getDataScopeByRoleAndMenu(Collections.singletonList(RoleConstant.ANONYMOUS_ID),menuCode);
                    dataScopes = BeanUtil.copyToList(list,DataScope.class);
                }

                chain.doFilter(addHeader(httpRequest, RoleConstant.ANONYMOUS, Fc.isNotEmpty(dataScopes)?JSON.toJSONString(dataScopes):StringPool.EMPTY), response);
                return;
            }
        }

        ResponseData<String> responseData = ReturnJsonUtil.print(HttpStatus.UNAUTHORIZED.value(),"缺失令牌，鉴权失败",false);
        WebUtil.renderJson((HttpServletResponse) response,responseData);

    }

    /**
     * 是否拥有权限
     * @Author mr.g
     * @param token TOKEN
     * @param currentPath 请求地址
     * @return boolean
     **/
    private boolean isAuthByToken(String token,String currentPath){
        Object o = redisUtil.get(CacheNames.TOKEN_URL_KEY + token);
        List<String> listUrl = Fc.isNull(o)?new ArrayList<>():(List<String>) o;
        return isAuth(listUrl, currentPath);
    }

    /**
     * 是否拥有权限
     * @Author mr.g
     * @param listUrl url
     * @param currentPath 请求地址
     * @return boolean
     **/
    private boolean isAuth(List<String> listUrl,String currentPath){
        if (CollUtil.safeContains(ShieldUtil.getUserRole(), ROOT_ID)){
            return Boolean.TRUE;
        }
        return listUrl.stream().anyMatch(pattern -> antPathMatcher.match(pattern, currentPath));
    }

    private boolean isSkip(String path) {
        return AuthDefExculdesUrl.getExculudesUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path))
                || authProperties.getSkipUrl().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));
    }

    private ServletRequest addHeader(HttpServletRequest request,String value, String dataScope) {
        MutableHttpServletRequest mutableRequest = new MutableHttpServletRequest(request);
        mutableRequest.putHeader(TokenConstant.PASS_HEADER_NAME, value);
        mutableRequest.putHeader(TokenConstant.DATA_SCOPE_NAME,dataScope);
        return mutableRequest;
    }

}
